/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor;

import java.util.Collection;

/**
 * A org.functor that takes one argument and returns a value.
 */
public abstract class UnaryFunction<ParamType,ReturnType> extends Functor {
	private static final long serialVersionUID = 0;

	/**
	 * Evaluate this function.
	 *
	 * @param obj the object to evaluate
	 * @return the result of this evaluation
	 */
	public abstract ReturnType evaluate(ParamType obj);

	/**
	 * Constructs a Function that always returns the same value.
	 */
	public static UnaryFunction constant(final Object value) {
		return new Constant(value);
	}

	/**
	 * Constructs a Function that returns the value passed to it.
	 */
	public static UnaryFunction identity() {
		return identity;
	}

	/** takes a method name and invokes that method without arguments on any object passed to it */
	public static UnaryFunction method(final String name) {
		return new UnaryFunction() {
			public Object evaluate(Object obj) {
				try {
					return obj.getClass().getMethod(name).invoke(obj);
				} catch (Exception e) {
					throw new RuntimeException("Exception accessing a method reflectively.", e);
				}
			}
		};
	}

	/**
	 * Take the result of this function and pass it to the predicate when run.
	 */
	public UnaryPredicate<ParamType> to(final UnaryPredicate<ReturnType> predicate) {
		return new UnaryPredicate<ParamType>() {
			public boolean test(ParamType obj) {
				return predicate.test(evaluate(obj));
			}
		};
	}

	/**
	 * Take the result of this function and pass it to the proc when run.
	 */
	public UnaryProcedure<ParamType> to(final UnaryProcedure<ReturnType> proc) {
		return new UnaryProcedure<ParamType>() {
			public void run(ParamType obj) {
				proc.run(evaluate(obj));
			}
		};
	}

	/**
	 * Take the result of this function and pass it to the function passed in.
	 * Returns the value from the function passed in.
	 */
	public <NewReturn> UnaryFunction<ParamType, NewReturn> to(final UnaryFunction<ReturnType, NewReturn> function) {
		final UnaryFunction<ParamType, ReturnType> me = this;
		return new UnaryFunction<ParamType, NewReturn>() {
			public NewReturn evaluate(ParamType obj) {
				return function.evaluate(me.evaluate(obj));
			}
		};
	}

	/**
	 * Take the result of this function see if it is the same as the object passed in.
	 */
	public UnaryPredicate isSame(Object asThisObject) {
		return to(UnaryPredicate.isSame(asThisObject));
	}

	/**
	 * Take the result of this function see if it is equal to the object passed in.
	 */
	public UnaryPredicate isEqual(ReturnType toThisObject) {
		return to(UnaryPredicate.isEqual(toThisObject));
	}

	public UnaryPredicate containedBy(final Collection<ReturnType> c) {
		return to(new UnaryPredicate<ReturnType>() {
			public boolean test(ReturnType obj) {
				return c.contains(obj);
			}
		});
	}

	/**
	 * Take the result of this function see if it is not equal to the object passed in.
	 */
	public UnaryPredicate isNotEqual(ReturnType toThisObject) {
		return to(UnaryPredicate.isNotEqual(toThisObject));
	}

	/**
	 * Take the result of this function see if it is null.
	 */
	public UnaryPredicate isNull() {
		return to(UnaryPredicate.isNull);
	}

	/**
	 * Take the result of this function see if it is not null.
	 */
	public UnaryPredicate isNotNull() {
		return to(UnaryPredicate.isNotNull);
	}

	/**
	 * When run executes the Function passed with no arguments.
	 */
	public static UnaryFunction dropAndExecute(final Function andExecute) {
		return new UnaryFunction() {
			public Object evaluate(Object obj) {
				return andExecute.evaluate();
			}
		};
	}

	/**
	 * Returns a function that will execute the wrapped function, passing the
	 * resulting value into the next execution, until the predicate matches the
	 * return value.
	 */
	public UnaryFunction recurse(final UnaryPredicate until) {
		final UnaryFunction me = this;
		return new UnaryFunction() {
			public Object evaluate(Object obj) {
				Object result = me.evaluate(obj);
				if (!until.test(result))
					return evaluate(result);
				return result;
			}
		};
	}

	/**
	 * Turns a UnaryFunction into a Function and binds its param to the value.
	 */
	public Function<ReturnType> bind(final ParamType value) {
		return new Binding(this, value);
	}

	public UnaryFunction<ParamType,ReturnType> lruCache(int lruSize) {
		return new FunctorCache.LRU<ParamType,ReturnType>(this, lruSize);
	}

	/**
	 * Returns a function that executes this function and the function passed in.
	 * Returns a 2 element collection containing the result of each of the
	 * functions.
	 */
	public UnaryFunction<ParamType, Tuple> combine(final UnaryFunction function) {
		final UnaryFunction<ParamType, ReturnType> me = this;
		return new UnaryFunction<ParamType, Tuple>() {
			public Tuple evaluate(ParamType obj) {
				return new Tuple(me.evaluate(obj), function.evaluate(obj));
			}
		};
	}

	/**
	 * Create a unary function for the given expression.
	 * @param forExpression The expression.
	 * @param paramClass The type of the functions parameter.
	 * @return A UnaryFunction that evaluates the expression.
	 */
	public static <P,R> UnaryFunction<P,R> create(String forExpression, Class paramClass, Class returnType) {
		return new DynamicUnaryFunction<P,R>(forExpression, paramClass, returnType);
	}

	/**
	 * Null function, just returns null.
	 */
	public static final UnaryFunction NULL = new NullFunction();

	public static class Constant <ValueType> extends UnaryFunction {
		private ValueType value;

		public Constant(ValueType value) {
			this.value = value;
		}

		public ValueType evaluate(Object obj) {
			return value;
		}

		public String toString() {
			return value != null ? value.toString() : "null";
		}
	}

	public static class NullFunction extends Constant {
		public NullFunction() {
			super(null);
		}
	}

	private static final UnaryFunction identity = new UnaryFunction() {
		public Object evaluate(Object obj) {
			return obj;
		}

		public String toString() {
			return "identity()";
		}
	};

	public static class Binding<ParamType,ReturnType> extends Function<ReturnType> {
		private final UnaryFunction<ParamType, ReturnType> function;
		private final ParamType value;

		public Binding(UnaryFunction<ParamType, ReturnType> me, ParamType value) {
			this.function = me;
			this.value = value;
		}

		public ReturnType evaluate() {
			return function.evaluate(value);
		}

		public String toString() {
			return function + "(" + value + ")";
		}
	}
}
